package com.tnovoselec.android.common.async;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import com.tnovoselec.android.common.async.listener.ResultListener;
import com.tnovoselec.android.common.async.notif.Notifier;
import com.tnovoselec.android.common.async.notif.NotifierFactory;
import com.tnovoselec.android.common.async.runner.ExecutorRunner;
import com.tnovoselec.android.common.async.runner.Runner;


public class Async {

	protected static final String TAG = Async.class.getSimpleName();

	private static final int CORE_POOL_SIZE = 4;
	private static final int MAXIMUM_POOL_SIZE = 32;
	private static final int KEEP_ALIVE = 10;

	private static final BlockingQueue<Runnable> WORK_QUEUE = new SynchronousQueue<Runnable>();

	private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() {

		private final AtomicInteger count = new AtomicInteger(1);

		@Override
		public Thread newThread(Runnable r) {
			Thread t = new Thread(r, Async.TAG + " #" + count.getAndIncrement());
			t.setPriority(Thread.NORM_PRIORITY - 1);
			return t;
		}
	};

	private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(
			Async.CORE_POOL_SIZE,
			Async.MAXIMUM_POOL_SIZE,
			Async.KEEP_ALIVE,
			TimeUnit.SECONDS,
			Async.WORK_QUEUE,
			Async.THREAD_FACTORY);

	private Async() {
		super();
	}

	/**
	 * Runs the specified future notifying the specified listener upon its completion.
	 * 
	 * @param key An arbitrary object to associate with the task. This
	 *        key will be passed to the specified result listener upon the
	 *        completion of the task, so can be used to identify the task
	 *        which has finished.
	 * @param future A future representing the task to execute.
	 * @param listener 
	 *        The listener who will be notified when when the task has
	 *        finished executing.
	 * @param notifier 
	 *        The notifier to use to notify the listener with. The choice of 
	 *        notifier is important for listeners who are not inherently thread 
	 *        safe. An easy choice on Android is to use 
	 *        {@link NotifierFactory#newThreadSpecificNotifier(android.os.Handler)}
	 *        with the UI thread's handler. This ensures that notifications take
	 *        place on the UI thread, so no special locking etc is needed to 
	 *        maintain thread safety.
	 * @return The future which was submitted.
	 */
	public static <K, T, F extends Future<T> & Runnable> F doAsync(K key, F future, ResultListener<K, T> listener, Notifier notifier) {
		return Async.newRunner(Async.EXECUTOR, notifier).doAsync(key, future, listener);
	}

	/**
	 * Runs the specified callable notifying the specified listener upon its completion.
	 * 
	 * @param key An arbitrary object to associate with the task. This 
	 *        key will be passed to the specified result listener upon the 
	 *        completion of the task, so can be used to identify the task 
	 *        which has finished.
	 * @param callable A callable representing the task to execute.
	 * @param listener 
	 *        The listener who will be notified when when the task has 
	 *        finished executing.
	 * @param notifier 
	 *        The notifier to use to notify the listener with. The choice of 
	 *        notifier is important for listeners who are not inherently thread 
	 *        safe. An easy choice on Android is to use 
	 *        {@link NotifierFactory#newThreadSpecificNotifier(android.os.Handler)}
	 *        with the UI thread's handler. This ensures that notifications take
	 *        place on the UI thread, so no special locking etc is needed to 
	 *        maintain thread safety.
	 * @return The future representing the future value of the computation, 
	 *         based on the callable which was provided.
	 */
	public static <K, T> Future<T> doAsync(K key, Callable<T> callable, ResultListener<K, T> listener, Notifier notifier) {
		return Async.newRunner(Async.EXECUTOR, notifier).doAsync(key, callable, listener);
	}

	/**
	 * Creates a {@link Runner runner} which executes tasks on the specified 
	 * executor, using the specified notifier to inform listeners of tasks 
	 * having finished.
	 * 
	 * @param executor The executor the runner will run tasks on
	 * @param notifier The notifier the runner will use to notify listeners
	 * @return A runner to which tasks can be submitted for execution.
	 */
	private static Runner newRunner(Executor executor, Notifier notifier) {
		if (executor == null) {
			throw new NullPointerException("Executor is null!");
		} else if (notifier == null) {
			throw new NullPointerException("Notifier is null!");
		}

		return new ExecutorRunner(executor, notifier);
	}
}
